﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Linq.Expressions;
using System.Windows.Forms;
using CloudShot.Editor;
using CloudShot.Editor.CanvasFigures;

namespace CloudShot.Utils.Tweens
{
  public class RunningAnimation
  {
    public IBoundingBoxFigure Obj { get; set; }
    public Tween Tween { get; set; }

    public bool IsActive 
    {
      get { return Tween.IsStarted; }
    }

    public bool IsSame(IBoundingBoxFigure obj, AnimationAdapter adapter)
    {
      if (Obj != obj)
        return false;

      return Tween.Adapter.Name == adapter.Name;
    }
  }

  public class Animator
  {
    private const int Duration = 300;
    private const int FrameTime = 30;
    private readonly IDrawingCanvas _canvas;

    private readonly Timer _timer = new Timer();

    private readonly List<RunningAnimation> _animations = new List<RunningAnimation>();
    private DateTime _currentTime;

    public Animator(IDrawingCanvas canvas)
    {
      _canvas = canvas;
      _timer.Interval = FrameTime;
      _timer.Tick += OnTick;
      _timer.Start();
      _timer.Enabled = false;
    }

    public void StartByteAnimation<T>(T obj, Expression<Func<T, byte>> expression, byte to, Action callBackAction = null, int duration = Duration) where T : IBoundingBoxFigure
    {
      StartAnimation(new ByteAnimationAdapter<T>(obj, expression, to), duration, callBackAction);
    }

    public void StartColorAnimation<T>(T obj, Expression<Func<T, Color>> expression, Color to, Action callBackAction = null, int duration = Duration) where T : IBoundingBoxFigure
    {
      StartAnimation(new ColorAnimationAdapter<T>(obj, expression, to), duration, callBackAction);
    }

    public void StartIntAnimation<T>(T obj, Expression<Func<T, int>> expression, int to, Action callBackAction = null, int duration = Duration) where T : IBoundingBoxFigure
    {
      StartAnimation(new IntAnimationAdapter<T>(obj, expression, to), duration, callBackAction);
    }

    public void StartRectangleAnimation<T>(T obj, Expression<Func<T, Rectangle>> expression, Rectangle to, Action callBackAction = null, int duration = Duration) where T : IBoundingBoxFigure
    {
      StartAnimation(new RectangleAnimationAdapter<T>(obj, expression, to), duration, callBackAction);
    }

    public void StartAnimation( AnimationAdapter adapter, int duration, Action callBackAction = null, EasingType easingType = EasingType.Sine)
    {
      RunningAnimation anim = _animations.FirstOrDefault(a => a.IsSame(adapter.Target, adapter));

      if (anim == null)
      {
        anim = new RunningAnimation()
        {
          Obj = adapter.Target
        };
        _animations.Add(anim);
        //Debug.WriteLine(string.Format("##StartColorAnimation adapter: {0}", adapter));
      }
      else
      {
        //Debug.WriteLine(string.Format("##Replace adapter: {0}", adapter));
      }

      anim.Tween = new Tween(Easing.EaseInOut, easingType, duration, adapter, callBackAction);
      anim.Tween.Start();

      if (!_timer.Enabled)
        _currentTime = DateTime.UtcNow;

      _timer.Enabled = true;
    } 

    public bool IsActive
    {
      get { return _animations != null && _animations.Any(a => a.IsActive); }
    }

    private void OnTick(object sender, EventArgs e)
    {
      var now = DateTime.UtcNow;
      double delta = (now - _currentTime).TotalMilliseconds;
      _currentTime = now;

      //Debug.WriteLine(delta);
   
      List<RunningAnimation> activeAnimations = _animations.Where(a => a.IsActive).ToList();

      using (new AutomaticCanvasRefresher(_canvas, activeAnimations.Select(x => x.Obj).GetGroupBoundingBox))
      {
        foreach (var animation in activeAnimations)
        {
          animation.Tween.Tick((int)delta);
        }
      }

      for (int index = _animations.Count - 1; index >= 0; --index)
      {
        var animation = _animations[index];

        //animation.IsActive can be changed in Tick()
        if (!animation.IsActive)
        {
          //Debug.WriteLine(string.Format("@@Remove Animation {0}", animation.Tween.Adapter));
          _animations.Remove(animation);
        }
      }

      if (_animations.Count == 0)
        _timer.Enabled = false;
    }
  }
}